<?php

class Html
{
	private $doctype = '<!DOCTYPE html>';
    private $lang = 'cn';
    private $title = '';
    private $head= '';
    private $arrMeta = array('<meta charset="UTF-8">');
    private $arrStyle = array();
    private $arrJs = array();
    private $body = '';
    private $indent = '    ';
    private $eol = PHP_EOL;
	
	public static function ini()
    {
        return new self;
    }
	
	public function setLang($lang)
    {
        $this->lang = $lang;
        return $this;
    }
    
    public function setTitle($title)
    {
        $this->title = $title;
        return $this;
    }
    
    /**
     * @param string $name 可选: author, description, keywords, generator, revised
     * @param string $content
     * @return self
     */
    public function setMetaName($name, $content)
    {
        $this->arrMeta[] = "<meta name='{$name}' content='{$content}'>";
        return $this;
    }
    
    /**
     * @param string $equiv 可选: content-type, expires, refresh, set-cookie
     * @param string $content
     * @return self
     */
    public function setMetaEquiv($equiv, $content)
    {
        $this->arrMeta[] = "<meta http-equiv='{$equiv}' content='{$content}'>";
        return $this;
    }
    
    /**
     * @param string $url <link rel='stylesheet' href='xxx.min.css'>
     * @return self
     */
    public function css($url)
    {
        $this->arrStyle[] = "<link rel='stylesheet' href='{$url}'>";
        return $this;
    }
    
    /**
     * @param string $url <script src="" type="text/javascript"></script>
     * @return self
     */
    public function js($url)
    {
        $this->arrJs[] = "<script type='application/javascript' src='{$url}'></script>";
        return $this;
    }
    
    /**
     * 组装 head 标签
     * @return $this
     */
    public function setHead()
    {
        $arrHead = array();
        
        $arrHead[] = '<head>';
        $arrHead[] = $this->indent.implode($this->eol.$this->indent, $this->arrMeta);
        $arrHead[] = $this->indent."<title> {$this->title} </title>";
        $arrHead[] = $this->indent.implode($this->eol.$this->indent, $this->arrStyle);
        $arrHead[] = $this->indent.implode($this->eol.$this->indent, $this->arrJs);
        $arrHead[] = '</head>';
        
        $this->head = implode($this->eol, $arrHead);
        
        return $this;
    }
    
    /**
     * @param $body
     * @return $this
     */
    public function setBody(body $body)
    {
        $this->body = $body->out();
        return $this;
    }
    
    /**
     * 输出HTML内容
     * @return string
     */
    public function out()
    {
        $str = $this->doctype.$this->eol;
        $str .= "<html lang='{$this->lang}'>".$this->eol;
        $str .= $this->head.$this->eol;
        $str .= $this->body.$this->eol;
        $str .= '</html>';
        
        return $str;
    }
    
}

/**
 * HTML标签公用属性, 内容, 缩进等
 * Trait attribute
 */
trait attribute
{
    private $text = ''; //文字内容
    
    public $indent = '    '; //缩进
    public $eol = PHP_EOL; //换行
    public $tag = '';
    
    public $arrAttr = array(); // array('data-x' => '123', 'align' => 'left', ...)
    
    /**
     * 获取赋了值的属性, 组装成字符串返回
     * @return string
     */
    public function getAttrs()
    {
        $attributes = array();
        if (!empty($this->arrAttr)) {
            $class = !empty($this->arrAttr['class']) ? $this->arrAttr['class'] : array();
            unset($this->arrAttr['class']);
            if (!empty($class)) {
                $str = implode(' ', $class);
                $attributes[] = "class='{$str}'";
            }
            
            foreach ($this->arrAttr as $k => $v) {
                $k = strval($k);
                $v = strval($v);
                
                $attributes[] = (strlen($v) == 0) ? "{$k}" : "{$k}='{$v}'";
            }
            
            return ' '.implode(' ', $attributes);
        } else {
            return '';
        }
    }
    
    /**
     * 每次调用out()方法后清空对象中的属性, 避免引起混乱
     */
    public function init()
    {
        $this->arrAttr = array();
    }
    
    public function setText($v)
    {
        $this->text = $v;
        return $this;
    }
    
    public function setAttr($k, $v)
    {
        $this->arrAttr[$k] = $v;
        return $this;
    }
    
    public function addClass($v)
    {
        $this->arrAttr['class'][] = $v;
        return $this;
    }
    
    
}

//一下是单独列出的标签类, 其中tag类是通用的可闭合标签类, 支持嵌套
// 还有些可闭合的标签单独成类: 例如p标签也是可闭合的, 但是单独列出来是为了显示时没有多余的换行, 不支持嵌套
//注释
class comment
{
    public $indent = '    ';
    public $eol = '';
    public $text;
    
    public static function ini()
    {
        return new self;
    }
    
    public function setIndent($indent)
    {
        $this->indent = $indent;
        return $this;
    }
    
    public function eol($eol)
    {
        $this->eol = $eol;
        return $this;
    }
    
    public function setText($text)
    {
        $this->text = $text;
        return $this;
    }
    
    public function out()
    {
        return $this->indent."<!-- {$this->text} -->".$this->eol;
    }
}

class label
{
    use attribute;
    
    public static function ini()
    {
        return new self;
    }
    
    public function out()
    {
        $str = '<label'.$this->getAttrs().'>'.$this->text.'</label>';
        $this->init();
        return $this->indent.$str;
    }
}

class a
{
    use attribute;
    
    public static function ini()
    {
        return new self;
    }
    
    public function out()
    {
        $str = '<a'.$this->getAttrs().'>'.$this->text.'</a>';
        $this->init();
        return $this->indent.$str;
    }
}

class p
{
    use attribute;
    
    public static function ini()
    {
        return new self;
    }
    
    public function out()
    {
        $str = '<p'.$this->getAttrs().'>'.$this->text.'</p>';
        $this->init();
        return $this->indent.$str;
    }
}

class img
{
    use attribute;
    
    public static function ini()
    {
        return new self;
    }
    
    public function out()
    {
        $str = '<img'.$this->getAttrs().'>';
        $this->init();
        return $this->indent.$str;
    }
}

class input
{
    use attribute;
    
    public static function ini()
    {
        return new self;
    }
    
    public function out()
    {
        $str = '<input'.$this->getAttrs().'>';
        $this->init();
        return $this->indent.$str;
    }
}

class checkbox
{
    use attribute;
    
    public static function ini()
    {
        return new self;
    }
    
    public function out()
    {
        $str = '<input'.$this->getAttrs().'>';
        $this->init();
        return $this->indent.$str;
    }
}

class radio
{
    use attribute;
    
    public static function ini()
    {
        return new self;
    }
    
    public function out()
    {
        $str = '<input'.$this->getAttrs().'>';
        $this->init();
        return $this->indent.$str;
    }
}

class select
{
    use attribute;
    
    public static function ini()
    {
        return new self;
    }
    
    public $arrChild = array();
    
    public function append(option $option)
    {
        $this->arrChild[] = $option;
        return $this;
    }
    
    //在顶部插入
    public function appendTop($child)
    {
        array_unshift($this->arrChild, $child);
        return $this;
    }
    
    public function out()
    {
        $str = '<select'. $this->getAttrs() .'>'.$this->eol;
        
        foreach ($this->arrChild as $k => $option) {
            $option->indent .= $this->indent;
            $this->arrChild[$k] = $option->out();
        }
        
        $str .= implode($this->eol, $this->arrChild);
        $str .= $this->eol.$this->indent.'</select>';
    
        $this->init();
        return $this->indent.$str;
    }
}

class option
{
    use attribute;
    
    public static function ini()
    {
        return new self;
    }
    
    public function out()
    {
        $str = '<option'.$this->getAttrs().'>'.$this->text.'</option>';
    
        $this->init();
        return $this->indent.$str;
    }
}

class form
{
    use attribute;
    
    const ENCTYPE_DEFAULT = 'application/x-www-form-urlencoded'; // 空格转换为 "+" 加号，特殊符号转换为 ASCII HEX 值
    const ENCTYPE_FILE = 'multipart/form-data'; // 文件上传
    const ENCTYPE_TEXT = 'text/plain'; // 空格转换为+, 其他特殊字符不做处理
    
    public $arrChild = array();
    
    public static function ini()
    {
        return new self;
    }
    
    /**
     * @param mixed $child 子元素对象, 必须有out方法, 并且返回HTML
     */
    public function append($child)
    {
        $this->arrChild[] = $child;
        return $this;
    }
    
    //在顶部插入
    public function appendTop($child)
    {
        array_unshift($this->arrChild, $child);
        return $this;
    }
    
    public function out()
    {
        $str = '<form'.$this->getAttrs().'>'.$this->eol;
        
        //补充缩进
        foreach ($this->arrChild as $ck => $child) {
            $child->indent .= $this->indent;
            $this->arrChild[$ck] = $child->out();
        }
        
        $str .= implode($this->eol, $this->arrChild);
        
        $str .= $this->eol.$this->indent.'</form>';
        
        $this->init();
        return $this->indent.$str;
    }
}

class table
{
    use attribute;
    
    public $arrChild = array();
    
    public static function ini()
    {
        return new self;
    }
    
    public function append($child)
    {
        $this->arrChild[] = $child;
        return $this;
    }
    
    //在顶部插入
    public function appendTop($child)
    {
        array_unshift($this->arrChild, $child);
        return $this;
    }
    
    public function out()
    {
        $str = '<table'.$this->getAttrs().'>'.$this->eol;
        
        foreach ($this->arrChild as $trk => $tr) {
            $tr->indent .= $this->indent;
            foreach ($tr->arrChild as $tdk => $td) {
                $td->indent .= $tr->indent;
                $tr->arrChild[$tdk] = $td->out();
            }
            
            $this->arrChild[$trk] = $tr->out();
        }
    
        $str .= implode($this->eol.$this->eol, $this->arrChild);
    
        $str .= $this->eol.$this->indent.'</table>';
    
        $this->init();
        return $this->indent.$str;
    }
}

class tr
{
    use attribute;
    
    public static function ini()
    {
        return new self;
    }
    
    public $arrChild = array();
    
    public function append($child)
    {
        $this->arrChild[] = $child;
        return $this;
    }
    
    //在顶部插入
    public function appendTop($child)
    {
        array_unshift($this->arrChild, $child);
        return $this;
    }
    
    public function out()
    {
        $str = '<tr'.$this->getAttrs().'>'.$this->eol;
        
        $str .= implode($this->eol, $this->arrChild);
        
        $str .= $this->eol.$this->indent.'</tr>';
    
        $this->init();
        return $this->indent.$str;
    }
}

class th
{
    use attribute;
    
    public static function ini()
    {
        return new self;
    }
    
    public function out()
    {
        $str = '<th'.$this->getAttrs().'>'.$this->text.'</td>';
        
        $this->init();
        return $this->indent.$str;
    }
}

class td
{
    use attribute;
    
    public static function ini()
    {
        return new self;
    }
    
    public function out()
    {
        $str = '<td'.$this->getAttrs().'>'.$this->text.'</td>';
        
        $this->init();
        return $this->indent.$str;
    }
}

//自定义闭合标签
class tag
{
    use attribute;
    
    public $arrChild = array();
    
    public static function ini($tag)
    {
        return new tag($tag);
    }
    
    public function __construct($tag)
    {
        $this->tag = $tag;
    }
    
    //在后边追加
    public function append($child, $key='')
    {
        if (!empty($key)) {
            $this->arrChild[$key] = $child;
        } else {
            $this->arrChild[] = $child;
        }
        return $this;
    }
    
    //在顶部插入
    public function appendTop($child)
    {
        array_unshift($this->arrChild, $child);
        return $this;
    }
    
    public function out()
    {
        $str = '<'.$this->tag.$this->getAttrs().'>'.$this->eol;
        
        $str .= $this->indent.$this->text.$this->eol;
        
        foreach ($this->arrChild as $ck => $child) {
            $child->indent .= $this->indent;
            $this->arrChild[$ck] = $child->out();
        }
        
        $str .= implode($this->eol, $this->arrChild);
        
        $str .= $this->eol.$this->indent."</{$this->tag}>";
        
        return $this->indent.$str;
    }
}

class body
{
    use attribute;
    
    public $arrChild = array();
    
    /**
     * @param mixed $child 子元素对象, 必须有out方法, 并且返回HTML文件
     * @return $this
     */
    public function append($child)
    {
        $this->arrChild[] = $child->out();
        return $this;
    }
    
    /**
     * @param mixed $child 子元素对象, 必须有out方法, 并且返回HTML文件
     * @return $this
     */
    public function appendTop($child)
    {
        array_unshift($this->arrChild, $child->out());
        return $this;
    }
    
    public function out()
    {
        $str = '<body>'.$this->eol;
        $str .= implode($this->eol.$this->eol, $this->arrChild);
        $str .= $this->eol.'</body>';
    
        return $str;
    }
}